let obj = {
    name: '啦啦啦',
    say: function (...args) {
        console.log(this.name);
        console.log(args);
    }
}
let obj2 = {
    name: '哈哈哈'
}

/**
 * call、apply、bind第一个参数是要指向this的那个对象，也就是在这个对象里面运行函数
 * call、apply区别：apply第二个参数是数组，call是有多个参数
 * bind函数在改变this指向后不运行函数，绑定时参数跟call一样，在运行bind绑定后的函数时，会把绑定时给的参数和运行时的参数合并
 */

// 手写call
obj.say.call(obj2, 'call', 2, 3);
Function.prototype.myCall = function (ctx, ...args) {
    // ctx上下文赋值
    if (ctx === null || ctx === undefined) {
        ctx = globalThis || window;
    }
    // 创建唯一属性值，防止污染源对象
    const fn = Symbol(0);
    // 把当前函数的this赋值给上下文中的函数
    ctx[fn] = this;
    // 由上下文中的函数调用，根据this指向规则：this指向运行时的函数本身，故达到将this指向ctx上下文中
    let result = ctx[fn](...args);
    // 运行结束后在这个绑定的对象里面删掉那个临时的唯一值，避免污染这个原始对象
    delete ctx[fn];
    return result;
}
obj.say.myCall(obj2, 'myCall', 2, 3);
console.log('---------------------------');


// 手写apply
obj.say.apply(obj2, ['apply', 2, 3]);
Function.prototype.myApply = function (ctx, args = []) {
    if (ctx === null || ctx === undefined) {
        ctx = globalThis || window;
    }
    if (!args || !Array.isArray(args)) {
        throw (new Error('参数不是数组'))
    }
    const fn = Symbol(0);
    ctx[fn] = this;
    let result = ctx[fn](...args);
    delete ctx[fn];
    return result;
}
obj.say.myApply(obj2, ['myApply', 2, 3]);
console.log('---------------------------');

// 手写bind
obj.say.bind(obj2, 'bind', 2)(3);
Function.prototype.myBind = function (ctx, ...args) {
    if (ctx === null || ctx === undefined) {
        ctx = globalThis || window;
    }
    const fn = Symbol(0);
    ctx[fn] = this;
    return (...newArgs) => {
        ctx[fn](...[...args, ...newArgs]);
        delete ctx[fn];
    }
}
obj.say.myBind(obj2, 'myBind', 2)(3);
